package indv.Cshen.cfengsecurityshirodemo.config;

import indv.Cshen.cfengsecurityshirodemo.realm.MobileRealm;
import indv.Cshen.cfengsecurityshirodemo.realm.UserNameRealm;
import lombok.Data;
import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.cache.CacheManager;
import org.apache.shiro.cache.MemoryConstrainedCacheManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthenticatingRealm;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.CookieRememberMeManager;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.servlet.Cookie;
import org.apache.shiro.web.servlet.SimpleCookie;
import org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.filter.DelegatingFilterProxy;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;

import javax.servlet.Filter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * @author Cfeng
 * @date 2022/7/19
 * Shiro的相关配置对象，包括Realm，SecurityManager，ShiroFilterFactoryBean请求过滤器工厂
 * 这里还是按照SpringBoot的思路，对应yaml中的配置，SpringBoot内置的很少，所以自定义配置shiro
 */

@Configuration //可以加上配置条件注解，只有满足条件才会创建其中的Bean
@ConditionalOnClass(value = {SecurityManager.class}) //只有导入了Shiro包存在SecurityManager对象才会创建该Config
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)//只有项目为Web项目才创建
@ConfigurationProperties(prefix = "shiro") //将配置的shiro参数注解注入，就不需要@Value逐一处理
@Data
public class ShiroConfig {
    //散列算法,加密方式
    private String hashAlgorithmName;

    //散列次数
    private int hashIterations;

    //默认的登录页面Shiro控制
    private String loginUrl;

    //anonUrls，不登录即可访问的页面,这里为一个String数组
    private String[] anonUrls;

    //authcUrls,权限urls，不要授权才能访问的页面
    private String[] authcUrls;

    //logoutUrl,登出页面
    private String logoutUrl;

    //委托给Servlet容器管理的过滤器的名称,也就是配置的FilterFactoryBean的名称
    private static final String SHIRO_FILTER = "shiroFilter";

    //thymeleaf结合Shiro，设置ShiroDialect对象名称
    private static  final  String SHIRO_DIALECT = "shiroDialect";
    /**
     * 声明凭证匹配器，Shiro会帮助匹配凭证也就是密码，认证过程中，一般密码是经过加密之后的，所以需要给出加密的方式和散列的次数
     */
    @Bean(name = "credentialsMatcher")
    public HashedCredentialsMatcher hashedCredentialsMatcher() {
        HashedCredentialsMatcher credentialsMatcher = new HashedCredentialsMatcher();
        credentialsMatcher.setHashAlgorithmName(hashAlgorithmName);
        credentialsMatcher.setHashIterations(hashIterations);
        return credentialsMatcher;
    }

    /**
     * 访问安全资源需要Realm桥梁，Realm认证过程使用凭证匹配器自动完成凭证匹配，所以依赖凭证
     */
    @Bean(name = "userNameRealm")
    public AuthorizingRealm getRealm(HashedCredentialsMatcher credentialsMatcher) {
        //创建一个Reaml交给SecurityManager用于访问资源
        //需要装载自定义凭证匹配器
        UserNameRealm realm = new UserNameRealm();
        realm.setCredentialsMatcher(credentialsMatcher);
        return realm;
    }

    @Bean(name = "mobileRealm")
    public AuthenticatingRealm getAuthenticatingRealm(HashedCredentialsMatcher credentialsMatcher) {
        //其他的Realm对象，其实可以直接让Spring创建，这里就直接创建
        MobileRealm mobileRealm = new MobileRealm();
        mobileRealm.setCredentialsMatcher(credentialsMatcher);
        return mobileRealm;
    }

    //配置SecurityManager对象,流程控制核心对象,依赖上面创建的Realm，这里可以设置优先级
    @Bean(name = "securityManager")
    public DefaultWebSecurityManager getSecurityManager(Realm userNameRealm, AuthenticatingRealm mobileRealm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        //必须知道数据源 realm【访问数据源的】
//        securityManager.setRealm(myRealm);
        //多Realm验证直接将所有Realm注入
        securityManager.setRealms(Arrays.asList(userNameRealm,mobileRealm));
        //设置多Realm策略,通过认证其设置
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        authenticator.setRealms(Arrays.asList(userNameRealm,mobileRealm));
        securityManager.setAuthenticator(authenticator);
        //设置记住我功能RemembermeManager
        CookieRememberMeManager rememberMeManager = new CookieRememberMeManager();
        Cookie cookie = new SimpleCookie("rememberMe");
        cookie.setHttpOnly(true);
        cookie.setMaxAge(318240000); //时长
        rememberMeManager.setCookie(cookie);
        securityManager.setRememberMeManager(rememberMeManager);
        //session管理一致
//        securityManager.setSessionManager();
        //认证缓存
        CacheManager cacheManager = new MemoryConstrainedCacheManager();
        securityManager.setCacheManager(cacheManager);
        return securityManager;
    }
    //配置ShiroFilterFactoryBean对象 请求过滤器
    @Bean(name = SHIRO_FILTER)
    public ShiroFilterFactoryBean  getShiroFilterFactoryBean(DefaultWebSecurityManager securityManager) {
        ShiroFilterFactoryBean filterFactoryBean = new ShiroFilterFactoryBean();
        //需要依赖安全管理器
        filterFactoryBean.setSecurityManager(securityManager);
        //设置未登录时跳转的页面,也就是authc资源会要求登录
        filterFactoryBean.setLoginUrl(loginUrl);
        //过滤器路径匹配,key为ant路径，支持匹配，value为Shiro默认过滤器包括anon，authc，logout
        Map<String,String> filterChainDefintionMap = new HashMap<>();
        //设置放行路径，不登陆检查
        if(anonUrls != null && anonUrls.length > 0) {
            for(String anon : anonUrls) {
                filterChainDefintionMap.put(anon,"anon");
            }
        }
        //设置登出路径
        if(logoutUrl != null) {
            filterChainDefintionMap.put(logoutUrl,"logout");
        }
        //设置拦截路径
        if(authcUrls != null && authcUrls.length > 0) {
            for(String authc : authcUrls) {
                filterChainDefintionMap.put(authc,"authc");
            }
        }
        //配置过滤器,上面的路径匹配都是String，要将String对应为Filter
        Map<String,Filter> filterMap = new HashMap<>();
        //上面的anon等都是Shiro内置的，不需要再配置，这里用于配置自定义
//        filterMap.put("oauth2", new Oauth2Filter())
        //将路径过滤器映射map装载，包括ChainMap和FilterMap
        filterFactoryBean.setFilters(filterMap);
        filterFactoryBean.setFilterChainDefinitionMap(filterChainDefintionMap);
//        filterFactoryBean.setUnauthorizedUrl("/common/unauthorized"); 配置方式的错误处理
        //返回此对象
        return filterFactoryBean;
    }

    @Bean
    //shiro委托过滤器，将Spirng中的过滤器注册到Servlet容器，代理类为DeleGatingFilterProxy过滤器代表代理类
    public FilterRegistrationBean<DelegatingFilterProxy> delegatingFilterProxy() {
        FilterRegistrationBean<DelegatingFilterProxy> filterRegistrationBean = new FilterRegistrationBean<>();
        //注册委托的Filter让Servlet管理
        DelegatingFilterProxy proxy = new DelegatingFilterProxy();
        //设置目标过滤器由Servlet容器管理，false表示由Spring容器管理
        proxy.setTargetFilterLifecycle(true);
        proxy.setTargetBeanName(SHIRO_FILTER);
        filterRegistrationBean.setFilter(proxy);
        //除了上面的写法还可以添加参数方式
//        filterRegistrationBean.setFilter(new DelegatingFilterProxy(SHIRO_FILTER));
//        filterRegistrationBean.addInitParameter("targetFilterLifecycle","true");
        return filterRegistrationBean;
    }
    /**
     * 使用Shiro的注解需要配置相关的切面通知的对象，和代理对象，adviser需要使用securityManager
     * 只有配置对象之后才能使用Shiro注解
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        //advisor的运行需要securityManager的协助
        advisor.setSecurityManager(securityManager);
        return advisor;
    }

    @Bean
    public DefaultAdvisorAutoProxyCreator getDefaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        //开启代理类功能
        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 如果要在thmeleaf中结合Shiro，那么需要shiroDalect；Spring Security也是可以放在其中的，类似
     * <p shiro:hasAllRoles="developer, 2">
     */
//    @Bean(name = SHIRO_DIALECT)
//    public ShiroDialect getShiroDialect() {
//        return new ShiroDialect();
//    }

    //===========其他的一些对象===================
    /**
     * 无权访问时的403页面设置SimpleMappingExceptionResolver 对象,其装载一个属性配置文件配置UnauthorizedException页面
     */
    @Bean
    public SimpleMappingExceptionResolver simpleMappingExceptionResolver() {
        SimpleMappingExceptionResolver resolver = new SimpleMappingExceptionResolver();
        Properties properties = new Properties();
        properties.setProperty("UnauthorizedException","/403.html");
        resolver.setExceptionMappings(properties);
        return resolver;
    }
}
